import numpy as np
import logging
from pathlib import Path
import json
import time

# 设置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger("Face-Recognizer")

class FaceRecognizer:
    """人脸识别比对类，负责将接收到的人脸特征与预存的人脸特征进行比对"""
    
    def __init__(self, similarity_threshold=0.75):
        """
        初始化人脸识别器
        
        参数:
            similarity_threshold (float): 相似度阈值，大于此值认为匹配成功
        """
        self.face_data = {}  # 存储学生人脸特征数据的字典 {student_id: {"features": [...], "name": "..."}}
        self.similarity_threshold = similarity_threshold
        logger.info(f"人脸识别器初始化完成，相似度阈值: {similarity_threshold}")
    
    def load_face_data(self, face_data):
        """
        加载人脸数据
        
        参数:
            face_data (dict): 包含学生人脸特征的字典
        """
        self.face_data = face_data
        student_count = len(face_data)
        logger.info(f"已加载 {student_count} 名学生的人脸数据")
    
    def recognize_face(self, face_data):
        """
        识别人脸，将接收到的人脸数据与已知的学生人脸数据进行比对
        
        参数:
            face_data (dict): 包含接收到的人脸特征数据的字典
            
        返回:
            str: 匹配到的学生ID，如果没有匹配则返回"unknown"
        """
        if not self.face_data:
            logger.warning("没有预先加载的人脸数据，无法进行比对")
            return "unknown"
        
        try:
            # 尝试解析接收到的特征数据
            if isinstance(face_data, str):
                face_data = json.loads(face_data)
            
            # 获取接收到的特征向量
            received_features = np.array(face_data.get("features", []))
            
            if len(received_features) == 0:
                # 如果没有直接提供特征向量，尝试从关键点计算
                landmarks = face_data.get("landmarks", {})
                if landmarks:
                    received_features = self._extract_features_from_landmarks(landmarks)
                else:
                    logger.error("接收到的数据中没有可用的特征向量或关键点")
                    return "unknown"
            
            # 计算与所有已知人脸的相似度
            best_match_id = "unknown"
            best_similarity = -1.0
            
            start_time = time.time()
            for student_id, student_data in self.face_data.items():
                # 获取学生的特征向量
                student_features = np.array(student_data.get("features", []))
                
                if len(student_features) > 0:
                    # 确保特征向量长度一致
                    min_length = min(len(received_features), len(student_features))
                    received_features_trimmed = received_features[:min_length]
                    student_features_trimmed = student_features[:min_length]
                    
                    # 计算余弦相似度
                    similarity = self._cosine_similarity(received_features_trimmed, student_features_trimmed)
                    
                    logger.debug(f"与学生 {student_id} 的相似度: {similarity:.4f}")
                    
                    # 更新最佳匹配
                    if similarity > best_similarity:
                        best_similarity = similarity
                        best_match_id = student_id
            
            elapsed_time = time.time() - start_time
            logger.info(f"人脸比对完成，耗时: {elapsed_time:.4f}秒")
            
            # 判断最佳匹配是否超过阈值
            if best_similarity >= self.similarity_threshold:
                student_name = self.face_data[best_match_id].get("name", "未知")
                logger.info(f"匹配成功！学生ID: {best_match_id}, 姓名: {student_name}, 相似度: {best_similarity:.4f}")
                return best_match_id
            else:
                logger.info(f"未找到匹配的学生，最佳相似度: {best_similarity:.4f}，低于阈值 {self.similarity_threshold}")
                return "unknown"
        
        except Exception as e:
            logger.error(f"人脸识别过程中出错: {str(e)}")
            return "unknown"
    
    def _extract_features_from_landmarks(self, landmarks):
        """
        从人脸关键点中提取特征向量
        
        参数:
            landmarks (dict): 包含人脸关键点的字典
            
        返回:
            np.array: 从关键点生成的特征向量
        """
        features = []
        
        # 提取眼睛、鼻子、嘴巴等关键点
        left_eye = landmarks.get("left_eye", [0, 0])
        right_eye = landmarks.get("right_eye", [0, 0])
        nose = landmarks.get("nose", [0, 0])
        mouth_left = landmarks.get("mouth_left", [0, 0])
        mouth_right = landmarks.get("mouth_right", [0, 0])
        
        # 计算关键点之间的距离作为特征
        # 眼间距离
        eye_distance = self._euclidean_distance(left_eye, right_eye)
        features.append(eye_distance)
        
        # 鼻子到眼睛的距离
        nose_to_left_eye = self._euclidean_distance(nose, left_eye)
        nose_to_right_eye = self._euclidean_distance(nose, right_eye)
        features.append(nose_to_left_eye)
        features.append(nose_to_right_eye)
        
        # 嘴巴宽度
        mouth_width = self._euclidean_distance(mouth_left, mouth_right)
        features.append(mouth_width)
        
        # 两眼到嘴的距离
        left_eye_to_mouth = self._euclidean_distance(left_eye, mouth_left)
        right_eye_to_mouth = self._euclidean_distance(right_eye, mouth_right)
        features.append(left_eye_to_mouth)
        features.append(right_eye_to_mouth)
        
        # 眼睛位置相对关系
        eye_angle = np.arctan2(right_eye[1] - left_eye[1], right_eye[0] - left_eye[0])
        features.append(eye_angle)
        
        # 计算相对比例（归一化）
        # 使用眼间距离作为归一化因子
        if eye_distance > 0:
            for i in range(len(features) - 1):  # 排除最后的角度
                features[i] /= eye_distance
        
        return np.array(features)
    
    def _euclidean_distance(self, point1, point2):
        """计算两点之间的欧氏距离"""
        return np.sqrt((point1[0] - point2[0])**2 + (point1[1] - point2[1])**2)
    
    def _cosine_similarity(self, vec1, vec2):
        """计算两个向量之间的余弦相似度"""
        # 处理零向量的情况
        if np.all(vec1 == 0) or np.all(vec2 == 0):
            return 0.0
        
        dot_product = np.dot(vec1, vec2)
        norm_vec1 = np.linalg.norm(vec1)
        norm_vec2 = np.linalg.norm(vec2)
        
        # 避免除零错误
        if norm_vec1 == 0 or norm_vec2 == 0:
            return 0.0
        
        similarity = dot_product / (norm_vec1 * norm_vec2)
        
        # 确保相似度在合理范围内
        return max(0.0, min(1.0, similarity))
    
    def _euclidean_similarity(self, vec1, vec2):
        """
        基于欧氏距离计算相似度，距离越小，相似度越高
        
        返回值范围：[0, 1]，1表示完全相同
        """
        distance = np.linalg.norm(vec1 - vec2)
        # 使用指数衰减将距离转换为相似度
        similarity = np.exp(-distance)
        return similarity

if __name__ == "__main__":
    # 测试代码
    recognizer = FaceRecognizer(similarity_threshold=0.7)
    
    # 模拟学生数据
    test_face_data = {
        "20226701": {
            "name": "张三",
            "features": [0.12, -0.45, 0.78, 0.32, -0.65]
        },
        "20226702": {
            "name": "李四",
            "features": [0.23, -0.36, 0.65, 0.41, -0.52]
        }
    }
    
    recognizer.load_face_data(test_face_data)
    
    # 模拟接收到的数据
    received_data = {
        "detection_result": [[62, 60, 202, 237]],
        "landmarks": {
            "left_eye": [107, 123],
            "right_eye": [170, 121],
            "nose": [144, 151],
            "mouth_left": [119, 197],
            "mouth_right": [167, 193]
        },
        "features": [0.13, -0.43, 0.77, 0.34, -0.63]  # 接近第一个学生的特征
    }
    
    # 测试识别
    result = recognizer.recognize_face(received_data)
    print(f"识别结果：{result}") 